package simpleftp.client.control;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//import java.util.StringTokenizer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

/*import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;*/

import simpleftp.client.gui.FTPClientWindow;

public class CommandDispatcher implements Runnable {
	public static final String NL = "\n";
	private static final String USER = "USER";
	private static final String PASS = "PASS";
	private static final String SERVER = "SERVER";
	private static final String PORT = "PORT";
	private static final String SETTINGS_ROOT = "simpleftpsettings";
	private static final String DEFAULT_SETTINGS = SETTINGS_ROOT + "/default";
	private static final String LAST_SETTINGS = SETTINGS_ROOT + "/last";
	private static final String DEF_USER = "paris";
	private static final String DEF_PASS = "paris";
	private static final String DEF_SERVER = "127.0.0.1";
	private static final String DEF_PORT = "7802";
	private final ExecutorService executorPool;
	private final CompletionService<Boolean> completionPool;
	private final FTPClientWindow window;
	private final int numberOfCores;
	private final double blockingCoefficient = 0.9;
	private final int poolSize;
	private BlockingQueue<Command> commands;
	private SimpleFTP client;
	private Preferences userPrefs;
	private String server;
	private String port;
	private String user;
	private String pass;
	private boolean alreadyConnected;

	public CommandDispatcher(BlockingQueue<Command> cmd, FTPClientWindow wind) {
		this.commands = cmd;
		this.window = wind;
		alreadyConnected = false;
		numberOfCores = Runtime.getRuntime().availableProcessors();
		poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
		executorPool = Executors.newFixedThreadPool(poolSize);
		completionPool = new ExecutorCompletionService<Boolean>(executorPool);
		client = new SimpleFTP();
	}

	@Override
	public void run() {
		try {
			loadStartupPreferences();
			Command cmd = this.commands.take();
			while (!cmd.equals(Command.QUIT)) {
				switch (cmd) {
				case CONNECT:
					connect();
					break;
				case DISCONNECT:
					disconnect();
					break;
				case STOR:
					upload();
					break;
				case LIST:
					listDir();
					break;
				case RETR:
					download();
					break;
				case PWD:
					pwd();
					break;
				case CWD:
					cwd();
					break;
				case CLEAR:
					clearConsole();
					break;
				case LOADPREFS:
					loadPreferences();
					break;
				case SAVEPREFS:
					savePreferences();
					break;
				case DELPREF:
					deletePreference();
					break;
				default:
					break;
				}
				cmd = this.commands.take();
			}
			saveLastPreferences();
			disconnect();
			executorPool.shutdown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void clearConsole() {
		window.console.setText("");
	}
	
	private void consoleToEnd() {
		window.console.setCaretPosition(window.console.getDocument().getLength());
	}

	private void saveLastPreferences() {
		userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
		userPrefs.put(USER, window.tfusername.getText());
		userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
		userPrefs.put(SERVER, window.tfservername.getText());
		userPrefs.put(PORT, window.tfport.getText());
		try {
			userPrefs.flush();
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void loadStartupPreferences() {
		try {
			userPrefs = Preferences.userRoot();
			if (!userPrefs.nodeExists(DEFAULT_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(DEFAULT_SETTINGS);
				userPrefs.put(USER, DEF_USER);
				userPrefs.put(PASS, DEF_PASS);
				userPrefs.put(SERVER, DEF_SERVER);
				userPrefs.put(PORT, DEF_PORT);
				userPrefs.flush();
			}
			if (userPrefs.nodeExists(LAST_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
			} 
			window.tfusername.setText(userPrefs.get(USER, DEF_USER));
			window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
			window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
			window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}
	
	private void loadPreferences() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to load:", "Load Preferences",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				window.tfusername.setText(userPrefs.get(USER, DEF_USER));
				window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
				window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
				window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
				
				window.console.append(NL + "User preferences " + s + " loaded.");
				consoleToEnd();
			} else {
				window.console.append(NL + "Preferences loading operation cancelled.");
				consoleToEnd();
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void savePreferences() {
		String s = (String) JOptionPane.showInputDialog(window,
				"Save the current connection info as:", "Save Preferences",
				JOptionPane.QUESTION_MESSAGE, null, null, null);
		if ((s != null) && (s.length() > 0)) {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
			userPrefs.put(USER, window.tfusername.getText());
			userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
			userPrefs.put(SERVER, window.tfservername.getText());
			userPrefs.put(PORT, window.tfport.getText());
			try {
				userPrefs.flush();
			} catch (BackingStoreException e) {
				window.console.append(NL + "Could not save preferences: "
						+ e.getMessage());
				consoleToEnd();
				e.printStackTrace();
			}
			window.console.append(NL + "Preferences saved as " + s);
			consoleToEnd();
		} else {
			window.console.append(NL + "Preferences saving operation cancelled.");
			consoleToEnd();
		}
	}
	
	private void deletePreference() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to delete:", "Delete Preference",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				userPrefs.removeNode();
				userPrefs.flush();
				window.console.append(NL + "User preferences " + s + " deleted.");
				consoleToEnd();
			} else {
				window.console.append(NL + "Preferences deletion operation cancelled.");
				consoleToEnd();
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}
	
	/*private void download() {
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			consoleToEnd();
			return;
		}
		DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                window.tree.getLastSelectedPathComponent();
		if (node == null) return;
		//Object nodeInfo = node.getUserObject();
		
		if (node.isLeaf()) {
			String stPath = window.tree.getSelectionPath().toString();
			//window.console.append(NL + stPath);
			consoleToEnd();
			stPath = stPath.replace("]", "");
			String path1[] = stPath.split(", ");
			String pathFinal = "";
			for(int i=2;i<path1.length;i++)
			{
				pathFinal += path1[i]; 
			}
			
			//String st = (String) nodeInfo;
			File toDown = new File(pathFinal);
			window.console.append(NL + pathFinal);
			consoleToEnd();
			
			final JFileChooser fc = new JFileChooser();
			fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			fc.setDragEnabled(true);
			int returnVal = fc.showDialog(window, "Download");
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				File dir = new File(fc.getSelectedFile().getAbsolutePath());
				List<Future<Boolean>> downloaders = new ArrayList<Future<Boolean>>();
				window.console.append(NL + dir.getAbsolutePath());
				consoleToEnd();
				
				Downloader downloader = new Downloader(toDown, dir, window, server, port, user,
						pass);
				downloaders.add(completionPool.submit(downloader));
			}
		} else {
			window.console.append(NL + "Please select a file.");
			consoleToEnd();
		}
	}*/
	
	private void download() {

		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			return;
		}

		DefaultMutableTreeNode newNode = (DefaultMutableTreeNode) window.tree
				.getLastSelectedPathComponent();

		if (newNode == null)
			return;

		//Object nodeInfo = newNode.getUserObject();

		if (newNode.isLeaf()) {
			
			final JFileChooser fc = new JFileChooser();
			fc.setFileSelectionMode(JFileChooser.SAVE_DIALOG);
			fc.setDragEnabled(true);
			int returnVal = fc.showDialog(window, "Download");
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				//String path = window.tree.getSelectionPath().toString();

				TreePath TreePathList[] = window.tree.getSelectionPaths();

				for (int i = 0; i < TreePathList.length; i++)
					System.out.println(TreePathList[i].toString());

				String SelectedFiles[] = new String[TreePathList.length];

				for (int i = 0; i < SelectedFiles.length; i++) {
					SelectedFiles[i] = standPath(TreePathList[i].toString());
					System.out.println(SelectedFiles[i]);
				}

				File[] files = new File[TreePathList.length];
				
				for (int i = 0; i < TreePathList.length; i++) {
					files[i] = new File(SelectedFiles[i]);
				}

				for (File f : files) {
						
					if (f.isFile()) {
						System.out.println("It's a file");
						List<Future<Boolean>> downloaders = new ArrayList<Future<Boolean>>(files.length);

						{
							Downloader downloader = new Downloader(f, window,server, port, user, pass, fc.getSelectedFile().getAbsolutePath());
							downloaders.add(completionPool.submit(downloader));
						}
					} 
					else
					{
						System.out.println("It's a folder");
						window.console.append(NL + "Please choose a file.");
					}
				}
			}
		} else {
			window.console.append(NL + "Download action cancelled.");
		}
		consoleToEnd();
    }

    private String standPath(String st) 
    {
        st = st.replace("]", "");
        String path[] = st.split(", ");
        String pathFinal = "";

        if(path.length > 2)
        {
        	for(int i=2;i<path.length;i++)
        	{
        		pathFinal += path[i];
        	}
        }
        
        if(path.length == 2)
        {
        	for(int i=1;i<path.length;i++)
        	{
        		pathFinal += path[i];
        	}
        	pathFinal += System.getProperty("file.separator") + ".." + System.getProperty("file.separator");
        }
        
        return pathFinal;
    }

	private void upload() {
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			consoleToEnd();
			return;
		}
		final JFileChooser fc = new JFileChooser();
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fc.setMultiSelectionEnabled(true);
		fc.setDragEnabled(true);
		int returnVal = fc.showDialog(window, "Upload");
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			File[] files = fc.getSelectedFiles();
			List<Future<Boolean>> uploaders = new ArrayList<Future<Boolean>>(
					files.length);
			for (File f : files) {
				Uploader uploader = new Uploader(f, window, server, port, user,
						pass);
				uploaders.add(completionPool.submit(uploader));
			}
		} else {
			window.console.append(NL + "Upload action cancelled.");
			consoleToEnd();
		}
	}
	
	
	/////////////////////////////////////////////////////
	//////////////////     FILE      ////////////////////
	/////////////////////////////////////////////////////
	/*private void listDir() {
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			consoleToEnd();
			return;
		}
		try {
			String dirContent = client.ls();
		    window.console.append(NL + dirContent);
		    consoleToEnd();
			File f = client.getFileTree();
			window.root.removeAllChildren();
			DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(f.getName());
			populateTree(userRoot, f);
			window.root.add(userRoot);
			//window.modelTree.reload();
			window.tree.expandRow(0);
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			consoleToEnd();
			e.printStackTrace();
		}

	}
	
	private void populateTree(DefaultMutableTreeNode userRoot, File root) throws IOException {
		// TO BE COMPLETED - WRITE THIS RECURSIVE METHOD - 7 LINES    
		
		File[] list = root.listFiles();
		if(list == null)
			System.out.println("NULL");
		else {
			DefaultMutableTreeNode subNode;
		    for(File nom : list)
		    {
		    	if(nom.isDirectory())
		    	{
		    		subNode = new DefaultMutableTreeNode(nom.getName()+System.getProperty("file.separator"));
		            this.populateTree(subNode, nom);
		    	}
		    	else
		    	{
		    		subNode = new DefaultMutableTreeNode(nom.getName());
		    	}
		    	userRoot.add(subNode);
		    }
		}
	}*/
	/////////////////////////////////////////////////////
	/////////////////     FIN FILE      /////////////////
	/////////////////////////////////////////////////////
	
	/////////////////////////////////////////////////////
	//////////////////     JSON      ////////////////////
	/////////////////////////////////////////////////////
	private void listDir() {
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			consoleToEnd();
			return;
		}
		try {
			String dirContent = client.ls();
		    window.console.append(NL + dirContent);
		    consoleToEnd();
		    JSONObject jsonob = client.getJSONObject();
			window.root.removeAllChildren();
			DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(jsonob.get("name"));
			populateTreeJSON(userRoot, jsonob);
			window.root.add(userRoot);
			window.tree.expandRow(0);
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			consoleToEnd();
			e.printStackTrace();
		}

	}
	
	private void populateTreeJSON(DefaultMutableTreeNode userRoot, JSONObject json)
	{
		JSONArray files = (JSONArray) json.get(json.get("name"));
		JSONObject object = null;
		DefaultMutableTreeNode newNode = null;
		
		if(files == null)
			System.out.println("NULL JSON");
		for(int i=0;i<files.size();i++)
		{
			object = (JSONObject)files.get(i);
			if(object.get("folder").toString() == "false")
			{
				newNode = new DefaultMutableTreeNode(object.get("name").toString());
			}
			if(object.get("folder").toString() == "true")
			{
				newNode = new DefaultMutableTreeNode(object.get("name").toString() + System.getProperty("file.separator"));
				this.populateTreeJSON(newNode, object);
			}
			userRoot.add(newNode);
		}
	}
	/////////////////////////////////////////////////////
	////////////////     FIN JSON      //////////////////
	/////////////////////////////////////////////////////

	private void pwd() {
		try {
			if (!alreadyConnected) {
				window.console.append(NL + "You are not connected to any server.");
				consoleToEnd();
				return;
			}
			String curDir = client.pwd();
			window.console.append(NL + "Current directory on FTP server: " + curDir);
			consoleToEnd();
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			consoleToEnd();
			e.printStackTrace();
		}
	}
	
	private void cwd()
	{
		try {
			if (!alreadyConnected) {
				window.console.append(NL + "You are not connected to any server.");
				consoleToEnd();
				return;
			}

			String st = window.tree.getSelectionPath().toString();
			
			//window.console.append(NL + st);
			//consoleToEnd();
			
			st = st.replace("]", "");
			String path1[] = st.split(", ");
			String pathFinal = "";
			
			if(path1.length > 2)
			{
				for(int i=2;i<path1.length;i++)
				{
					pathFinal += path1[i]; 
				}
			}
			if(path1.length == 2)
			{
				for(int i=1;i<path1.length;i++)
				{
					pathFinal += path1[i];
				}
				pathFinal += System.getProperty("file.separator") + ".." + System.getProperty("file.separator");
			}
			
			File fnewRoot = new File(pathFinal);
			
			//window.console.append(NL + "path final: " + pathFinal);
			//consoleToEnd();
			
			if(fnewRoot.isDirectory())
			{
				client.cwd(pathFinal);
				window.console.append(NL + "New Current directory: " + pathFinal);
				consoleToEnd();
			}
			
			
			
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			consoleToEnd();
			e.printStackTrace();
		}
	}

	private void disconnect() {
		try {
			if (alreadyConnected) {
				client.disconnect();
				alreadyConnected = false;
				window.console.append(NL + "Now disconnected");
				consoleToEnd();
			} else {
				window.console.append(NL + "Already disconnected");
				consoleToEnd();
			}
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			consoleToEnd();
			e.printStackTrace();
		}

	}

	private void connect() {
		if (alreadyConnected) {
			window.console.append(NL
					+ "You are already connected to this server.");
			consoleToEnd();
			return;
		}
		server = window.tfservername.getText();
		if (server == null || "".equals(server)) {
			window.console.append(NL
					+ "You must specify the server IP address.");
			consoleToEnd();
			return;
		}
		port = window.tfport.getText();
		if (port == null || "".equals(port)) {
			window.console.append(NL
					+ "You must specify the server port I must connect to.");
			consoleToEnd();
			return;
		}
		user = window.tfusername.getText();
		if (user == null || "".equals(user))
			user = "anonymous";
		pass = new String(window.pfuserpass.getPassword());
		if (pass == null || "".equals(pass))
			pass = "anonymous";
		try {
			client.connect(server, Integer.parseInt(port), user, pass);
			alreadyConnected = true;
			window.console.append(NL + "Now connected");
			consoleToEnd();
			pwd();
			listDir();
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			consoleToEnd();
			e.printStackTrace();
		} catch (NumberFormatException nfe) {
			window.console.append(NL + nfe.getMessage());
			consoleToEnd();
			nfe.printStackTrace();
		}
	}
}
